package puntaelena.facilitylocation.hillclimbing.experiment.grszcompetitive;

import puntaelena.graphsupport.FacilitiesGraph;
import puntaelena.graphsupport.GenerateGrSettings;
import puntaelena.graphsupport.GraphSupportFront;
import puntaelena.graphsupport.solver.IFacilityLocationSolver;
import puntaelena.facilitylocation.hillclimbing.solver.services.ICoalitionsListener;
import puntaelena.facilitylocation.hillclimbing.solver.services.IServicesFcltLocSolver;
import puntaelena.facilitylocation.hillclimbing.solver.services.IFcltCoalitionEventsListener;
import puntaelena.facilitylocation.hillclimbing.solver.agents.FacilityCoalition;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import jfuzzyprincepless.negotiation.services.IService;

import laboratoryQ.computation.BaseUtils;
import laboratoryQ.experiment.IExperiment;
import laboratoryQ.experiment.IExperimentResult;
import laboratoryQ.experiment.process.IExperimentContext;

public class PElenaGSCExperiment
	implements IExperiment, IFcltCoalitionEventsListener,
	ICoalitionsListener
{
	private List<Integer> coalitionSizes = new ArrayList<Integer>();
	private List<Integer> enemiesSizes = new ArrayList<Integer>();

	@Override
	public IExperimentResult runExperiment(
		IExperimentContext icontext,
		IExpRunStatusHandler expRunStatusHandler
	) {
		findProfitableEnemiesBeg = -1;
		totalFindProfitableEnemies = 0;
		computeTryApplyProfitBeg = -1;
		totalComputeTryApplyProfit = 0;
		searchBestCoalitionPairBeg = -1;
		totalSearchBestCoalitionPair = 0;
		cityBeg = -1;
		totalCity = 0;
		long experimentBegin = System.currentTimeMillis();
		long totalHCSolver = 0;
		
		PElenaGSCExperimentContext context =
			(PElenaGSCExperimentContext) icontext;
		PElenaGSCExperimentSettings settings =
			context.getExperimentSettings();

		int citiesCountBeg = settings.citiesCountBeg;

		int citiesCountEnd = settings.citiesCountEnd;
		int citiesRange = citiesCountEnd -
			citiesCountBeg + 1;
		int facilitiesRange = settings.facilitiesCountEnd -
			settings.facilitiesCountBeg + 1;

		List<double[]> result = new ArrayList<double[]>();
		int solversCount = settings.solvers.length;
		for (int i = 0; i < solversCount; i++) {
			result.add(new double[citiesRange]);
		}

		int[] coalitionsReport = new int[citiesRange];
		int[] enemiesReport = new int[citiesRange];

		GenerateGrSettings genset = new GenerateGrSettings();
		genset.neighborCountBeg = settings.neighborCountBeg;
		genset.neighborCountRange = settings.neighborCountRange;
		genset.cheapEdgeBeg = settings.cheapEdgeBeg;
		genset.cheapEdgeRange = settings.cheapEdgeRange;
		genset.expensiveEdgeBeg = settings.expensiveEdgeBeg;
		genset.expensiveEdgeRange = settings.expensiveEdgeRange;
		genset.facilityPriceBeg = settings.facilityPriceBeg;
		genset.facilityPriceRange = settings.facilityPriceRange;
		genset.triangleIneqEnabled = settings.triangleIneqEnabled;

		for (
			int citiesCount = citiesCountBeg;
			citiesCount <= citiesCountEnd;
			citiesCount++
		) {
			int experimentNumber = citiesCount - citiesCountBeg;
			
			for (
				int runCount = 0;
				runCount < settings.eachExperimentRunsCount;
				runCount++
			) {
				genset.randomSeed = context.getRandom().get() * 10000;
	
				int facilitiesCount = settings.facilitiesCountBeg;
				facilitiesCount += ((double)experimentNumber) *
					facilitiesRange / citiesRange;
				if (
					citiesCount == citiesCountEnd &&
					facilitiesCount < settings.facilitiesCountEnd
				) {
					throw new RuntimeException("Unexpected");
				}
	
				genset.firstBipart = citiesCount;
				genset.secondBipart = facilitiesCount;
	
				FacilitiesGraph fgraph = (FacilitiesGraph) settings
					.getGraphGenerator().generate(genset);
				boolean[] isfcltOpen = new boolean[facilitiesCount];
				int[] witnesses = new int[citiesCount];
	
				for (int i = 0; i < settings.solvers.length; i++) {
					IFacilityLocationSolver solver = settings.solvers[i];
					Arrays.fill(isfcltOpen, false);
					Arrays.fill(witnesses, -1);
	
					if (solver instanceof IServicesFcltLocSolver) {
						coalitionSizes.clear();
						enemiesSizes.clear();
	
						IFcltCoalitionEventsListener fceListen =
							this;
	//						new NOPFcltCoalitionEventsListener();
						IService performanceListener =
							i == 4 ?
							this :
							ICoalitionsListener.NOP_LISTENER;
						if (i == 4) totalHCSolver -= System.currentTimeMillis();
						((IServicesFcltLocSolver)solver)
							.solve(fgraph, isfcltOpen, witnesses, fceListen, performanceListener);
						if (i == 4) totalHCSolver += System.currentTimeMillis();
	
	
						int n = coalitionSizes.size();
						for (int j = 0; j < n; j++) {
							coalitionsReport[experimentNumber] +=
								BaseUtils.sqare(coalitionSizes.get(j) - 1);
						}
						for (int enemiesSize : enemiesSizes) {
							enemiesReport[experimentNumber] +=
								BaseUtils.sqare(enemiesSize - 1);
						}
					} else {
						solver.solve(fgraph, isfcltOpen, witnesses);
					}
	
					double cost = GraphSupportFront.computeFacilityLocationCost(
						fgraph,
						isfcltOpen,
						witnesses
					);// TODO: single names 4 witness - isfcltopen
					result.get(i)[experimentNumber] += cost;
				}
			}

			expRunStatusHandler.stepsProcessed(
				experimentNumber,
				citiesRange
			);
		}

		context.graphSizeCosts = result;
		context.coalitionsReport = coalitionsReport;
		context.enemiesReport = enemiesReport;
		
		long experimentTime = System.currentTimeMillis() -
			experimentBegin;
		System.out.println(
			"PElenaGSCExperiment: experiment-time: " +
			experimentTime +
			"\t\t find-frofitable-enemies-time: " +
			totalFindProfitableEnemies + 
			"\t\t compute-try-applyProfit " +
			totalComputeTryApplyProfit +
			"\t\t search-best-coalition-pair " +
			totalSearchBestCoalitionPair +
			"\t\t total-HC-solver " +
			totalHCSolver +
			"\t\t city " +
			totalCity +
			"\t\t __iters " +
			(treeSoftUpdates + treeRrebuilds) +
			"\t\t __rebuild " +
			treeRrebuilds
		);

		expRunStatusHandler.handleExperimentResult(context);
		return context;
	}

	/**
	 * ### ### ### ### ### ### ### ###
	 * IFcltCoalitionEventsListener
	 * ### ### ### ### ### ### ### ###
	 */

	// ICoalitionsRegistry events:
	public void onMostSimilarCoalitionsChanged(
		double oldSimMeasure,
		FacilityCoalition oldCoalA,
		FacilityCoalition oldCoalB,
		double simMeasure,
		FacilityCoalition coalA,
		FacilityCoalition coalB
	) {
	}

	@Override
	public void onCoalitionsUnite(
		FacilityCoalition coalA,
		FacilityCoalition coalB
	) {
	}

	@Override
	public void onNoUnite(double benefitMeasure) { }

	// CollaboratingFacilityAgent events:
	@Override
	public void onCoalitionApply(
		FacilityCoalition coalition,
		List<Integer> enemies
	) {
		coalitionSizes.add(coalition.getMembers().size());
		enemiesSizes.add(enemies.size());
	}
	
	// ICoalitionsListener
	
	private long findProfitableEnemiesBeg = -1;
	private long totalFindProfitableEnemies = 0;

	@Override
	public void onFindProfitableEnemiesBeg() {
		assert findProfitableEnemiesBeg == -1;
		findProfitableEnemiesBeg = System.currentTimeMillis();
	}

	@Override
	public void onFindProfitableEnemiesEnd() {
		assert findProfitableEnemiesBeg > -1;
		totalFindProfitableEnemies += System.currentTimeMillis() -
			findProfitableEnemiesBeg;
		findProfitableEnemiesBeg = -1;
	}

	private long computeTryApplyProfitBeg = -1;
	private long totalComputeTryApplyProfit = 0;
	
	@Override
	public void onComputeTryApplyProfitBeg() {
		assert computeTryApplyProfitBeg == -1;
		computeTryApplyProfitBeg = System.currentTimeMillis();
	}

	@Override
	public void onComputeTryApplyProfitEnd() {
		assert computeTryApplyProfitBeg > -1;
		totalComputeTryApplyProfit += System.currentTimeMillis() - computeTryApplyProfitBeg;
		computeTryApplyProfitBeg = -1;
	}
	
	private long searchBestCoalitionPairBeg = -1;
	private long totalSearchBestCoalitionPair = 0;
	
	@Override
	public void onSearchBestCoalitionPairBeg() {
		assert searchBestCoalitionPairBeg == -1;
		searchBestCoalitionPairBeg = System.currentTimeMillis();
	}
	@Override
	public void onSearchBestCoalitionPairEnd() {
		assert searchBestCoalitionPairBeg > -1;
		totalSearchBestCoalitionPair += System.currentTimeMillis() - searchBestCoalitionPairBeg;
		searchBestCoalitionPairBeg = -1;
	}
	
	private long cityBeg = -1;
	private long totalCity = 0;
	
	@Override
	public void onCityBeg() {
		assert cityBeg == -1;
		cityBeg = System.currentTimeMillis();
	}

	@Override
	public void onCityEnd() {
		assert cityBeg > -1;
		totalCity += System.currentTimeMillis() - cityBeg;
		cityBeg = -1;
	}

	private int treeRrebuilds;
	private int treeSoftUpdates;

	@Override
	public void onTreeRebuild() {
		treeRrebuilds++;
	}

	@Override
	public void onTreeSoftUpdate() {
		treeSoftUpdates++;
	}
}
